import re
from functools import wraps
from html import escape
from collections import namedtuple
# from webob import Response
from webob.dec import wsgify
from webob.exc import HTTPNotFound, HTTPTemporaryRedirect

PATTERNS = {
    'str': '[^/].+',
    'word': '\w+',
    'any': '.+',
    'int': '[+-]?\d+',
    'float': '[+-]?\d\.\d+'
}

CASTING = {
    'str': str,
    'word': str,
    'any': str,
    'int': int,
    'float': float
}

Route = namedtuple('Route', ['pattern', 'methods', 'casting', 'handler'])


class Router:
    def __init__(self, prefix='', domain=None, filters=None):
        self.routes = []
        self.domain = domain
        self.prefix = prefix
        if filters is None:
            filters = []
        self.filters = []
        for fl in filters:
            if isinstance(fl, Filter):
                self.filters.append(fl)
            else:
                raise Exception('{} is not a Filter'.format(fl))

    def _route(self, rule, methods, handler):
        pattern, casting = self._rule_parse(rule)
        self.routes.append(Route(re.compile(pattern),
                                 methods, casting, handler))

    def _rule_parse(self, rule):
        # TODO
        pattern = []
        spec = []
        casting = {}
        is_spec = False
        for c in rule:
            if c == '{' and not is_spec:
                is_spec = True
            elif c == '}' and is_spec:
                is_spec = False
                name, p, c = self._spec_parse(''.join(spec))
                spec = []
                pattern.append(p)
                casting[name] = c
            elif is_spec:
                spec.append(c)
            else:
                pattern.append(c)
        return '{}$'.format(''.join(pattern)), casting

    def _spec_parse(self, src):
        tmp = src.split(':')
        if len(tmp) > 2:
            raise Exception('error pattern')
        name = tmp[0]
        name_type = 'str'
        if len(tmp) == 2:
            name_type = tmp[1]
        pattern = '(?P<{}>{})'.format(name, PATTERNS[name_type])
        return name, pattern, CASTING[name_type]

    def route(self, pattern, methods=None):
        if methods is None:
            methods = ('GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTION')

        def dec(fn):
            self._route(pattern, methods, fn)
            return fn
        return dec

    def _domain_match(self, request):
        return self.domain is None or re.match(self.domain, request.host)

    def _prefix_match(self, request):
        return request.path.startswith(self.prefix)

    def _apply_filter(self, handler):
        @wraps(handler)
        def apply(ctx, request):
            for fl in self.filters:
                request = fl.before_request(ctx, request)
            response = handler(ctx, request)
            for fl in reversed(self.filters):
                response = fl.after_request(ctx, response)
            return response
        return apply

    def match(self, request):
        if self.domain is None or re.match(self.domain, request.host()):
            if request.path.startswith(self.prefix):
                for route in self.routes:
                    if request.method in route.methods:
                        m = route.pattern.match(
                            request.path.replace(self.prefix, '', 1))
                        if m:
                            request.args = {}
                            for k, v in m.groupdict().items():
                                request.args[k] = route.casting[k](v)
                            return self._apply_filter(route.handler)


class Application:
    def __init__(self, routers=None, **options):
        if routers is None:
            self.routers = []
        self.routers = routers
        self.options = options

    def add_router(self, router):
        self.routers.append(router)

    @wsgify
    def __call__(self, request):
        for router in self.routers:
            handler = router.match(request)
            if handler:
                return handler(self, request)
        raise HTTPNotFound(detail='no handler match')


# add filter
class Filter:
    def before_request(self, ctx, request):
        return request

    def after_request(self, ctx, response):
        return response
